// FLTK implementation of an image view widget
// Copyright 2005 (C) Ralph Thomas

#include <view/image.h>
#include <fltk/Group.h>
#include <fltk/Style.h>
#include <fltk/SharedImage.h>
#include <fltk/damage.h>
#include <toolkit/fltk/aspectImage.h>
#include <fltk/draw.h>
#include <fltk/x.h>

#include <binder.hpp>

#include <iostream>
#include <sstream>

namespace view {
	//
	/// The imageView class wraps an fltk::Widget with an interface which
	/// works with ASL.
	//
	class imageView {
		//
		/// This widget will draw an image with the correct size,
		/// whereas the normal fltk::Widget doesn't.
		//
		class imageWidget : public fltk::Group {
		  public:
			imageWidget() : fltk::Group( 0, 0, 10, 10 ) {
				box( fltk::DOWN_BOX );
				color( fltk::BLACK );
			}
			virtual void draw() {
				uchar d( damage() );
				if( d & (fltk::DAMAGE_CONTENTS | fltk::DAMAGE_ALL) ) {
					draw_background();
					draw_box();
					fltk::Rectangle r( 0, 0, w(), h() );
					if( box() ) box()->inset( r );
					//
					// Make sure that the image doesn't draw outside of
					// our bounds.
					//
					if( image() ) image()->_draw( r );
				}
				//
				// Draw the child widgets.
				//
				fltk::Group::draw();
			}
			virtual void layout() { fltk::Widget::layout(); }
		};
		fltk::Group*		_widget;///< The FLTK Widget.
		fltk::SharedImage*	_scale; ///< Scaled image.
		fltk::AspectImage	_aspect;///< Aspect preserving adaptor.
		unsigned char*		_data;	///< Image data.
		adobe::binder_helper_t	_binder;///< Model binder.
		//
		// void _update( const adobe::value_t& value )
		//
		/// This function gets called when the value of the image in the
		/// sheet changes. It draws the new image in place of the old
		/// one.
		///
		/// \param	value	the new image to draw.
		//
		void _update( const adobe::value_t& value ) {
			try {
				//
				// Hopefully the data is a string. Currently the
				// icon format for FLTK is ill defined (and
				// dangerous!).
				//
				std::string data = value.get<std::string>();
				if( _scale ) {
					_scale->destroy();
					_scale->remove();
					_scale = 0;
				}
				//
				// If we have some data then erase it. FLTK
				// will not delete it for us.
				//
				if( _data ) { delete[] _data; _data = 0; }
				//
				// Urgh. There's a bug in FLTK's JPEG reading code. It
				// can memcpy over the end of the buffer (because it
				// doesn't know where the end of the buffer is!). This
				// MUST be fixed in FLTK soon -- for now this hack
				// covers it for non corrupt JPEGs (which could still
				// crash us here).
				//
				_data = new unsigned char[data.size() + 4096];
				for( unsigned int i = 0; i < data.size() + 4096; i++ ) _data[i] = 0;
				memcpy( _data, data.c_str(), data.size() );
				//
				// Ensure that we have some data. FLTK's JPEG
				// loader is somewhat flakey when it's given
				// empty data.
				//
				_scale = fltk::getStaticImage( _data );
			} catch( ... ) {
				//
				// Well, that didn't work; we would try to get
				// the icon here, but the FLTK icon API isn't
				// good enough yet.
				//
				if( _scale ) { 
					_scale->destroy();
					_scale->remove();
					_scale = 0;
				}
				if( _data ) { delete[] _data; _data = 0; }
			}
			//
			// Tell the widget to use the new image we loaded.
			//
			_aspect.setSource( _scale );
			_widget->image( &_aspect );
			_widget->redraw();
		}
	  public:
		//
		// imageView( ... )
		//
		/// This constructor will create and initialize a new image view
		/// widget, assuming that the given parameters are correct.
		///
		/// \param	parameters	parameters to create the new
		///				view with.
		/// \param	token		the factory token to create
		///				against.
		/// \param	parent		the parent widget to insert
		///				into.
		//
		imageView( const adobe::dictionary_t& parameters,
			const adobe::factory_token_t& token,
			const adobe::widget_node_t& parent )
		: _widget( 0 ), _scale( 0 ), _data( 0 ), _binder( token.sheet_m ) {
			//
			// We need to find out which cell contains the image,
			// and then bind to it.
			//
			adobe::value_t bind =
				parameters.at( adobe::static_name_t( "bind" ) );
			//
			// We may now create our widget. It has no label and
			// (initially) no real dimensions.
			//
			_widget = new imageWidget();
			//
			// Bind to whereever we are getting updates from.
			//
			_binder.initialize( bind,
				boost::bind( &imageView::_update, this, _1 ), 0 );
		}
		//
		// ~imageView()
		//
		/// Destroy the image view widget, and any cached image data.
		//
		~imageView() {
			if( _widget && !_widget->parent() ) delete _widget;
			if( _scale ) {
				_scale->destroy();
				_scale->remove();
				_scale = 0;
			}
			if( _data ) delete[] _data;
		}
		//
		// adobe::extents_t best_bounds()
		//
		/// This function ought to return the best bounds for this
		/// widget. It can't know until it gets told the size of the
		/// image, though (so it provides a bad guess until then).
		///
		/// \return	a guess at bounds.
		//
		adobe::extents_t best_bounds() {
			adobe::extents_t result;
			int w = 100, h = 100;
			result.width() = w;
			result.height() = h;
			return result;
		}
		//
		// void place( const adobe::point_2d_t& position,
		//		const adobe::extents_t& geometry )
		//
		/// Place the widget in the given location under it's parent.
		///
		/// \param	position	the desired parent-relative
		///				position for this image view.
		/// \param	geometry	the desired geometry for this
		///				image view.
		//
		void place( const adobe::point_2d_t& position,
			const adobe::extents_t& geometry ) {
			assert( _widget );
			_widget->resize( position.x_m, position.y_m,
				geometry.width(), geometry.height() );
		}
		//
		// fltk::Widget* getWidget() const
		//
		/// Return the widget which draws the image for this image view.
		///
		/// \return	the image view widget.
		//
		fltk::Widget* getWidget() const { return _widget; }
	};

	adobe::widget_node_t image_factory(
		const adobe::dictionary_t&	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token ) {
		//
		// Create an imageView widget, and set it to delete when the
		// assemblage vanishes.
		//
		imageView* iv = new imageView( parameters, token, parent );
		token.assemblage_m.delete_on_destruction( iv );
		//
		// Insert this widget into it's parent on the main display.
		//
		fltk::Widget* w = iv->getWidget();
		adobe::display_t::position_t display_token( adobe::insert(
			adobe::get_main_display(), parent.display_token_m,
			w ) );
		adobe::display_t::position_t overlay_token(
			token.overlay_m.insert( parent.overlay_token_m ) );
		//
		// Bind the calculate and place signals for Eve.
		//
		adobe::eve_t::signal_suite_t signals;
		signals.eve_calculate_proc_m = boost::bind(
			&eve_client::utility_widget_calculate,
			iv->best_bounds(), _1 );
		eve_client::place_func_t placer(
			boost::bind( &imageView::place, iv, _1, _2 ) );
		signals.eve_place_proc_m = boost::bind(
			&eve_client::utility_widget_place,
			placer, _1, _2,
			boost::ref( token.overlay_m ), overlay_token );
		//
		// Containers have to provide non-default eve calculation data.
		//
		// XXX: constant values should come from the widget's box.
		//
		adobe::eve_t::calculate_data_t cdata;
		cdata.placement_m = adobe::eve_t::place_row;
		cdata.horizontal().margin_m.first = 4;
		cdata.horizontal().margin_m.second = 4;
		cdata.vertical().margin_m.first = 4;
		cdata.vertical().margin_m.second = 4;
		//
		// Register the widget with the layout engine.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					cdata,
					true,
					parameters,
					signals )
				)
			);
		//
		// Return the new image view.
		//
		return adobe::widget_node_t( parent.size_m, eve_token,
			display_token, overlay_token );
	}
};

